#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <limits.h>
#include <sys/timeb.h>

#include <omp.h>
#include <mpi.h>

#include "preprocessor.h"
#include "avl.h"

#define ROOT 0
#define TIMEOUT 600

/* Ponteiro para o arquivo que contera os palavras encontradas */
FILE * pFile;

/* Estrutura que representa o comunicador universal */
MPI_Comm comm = MPI_COMM_WORLD;
/* Variavel que contém o status das operacoes que serao realizadas */
MPI_Status status;

/* Arvores primarias */
/* Arvore primaria que contem as palavras completas */
node * words = NULL;
/* Arvore primaria que contem de 0 a 5 letras */
node * words5 = NULL;
/* Arvore primaria que contem de 5 a 10 letras */
node * words10 = NULL;
/* Arvore primaria que contem de 10 a 15 letras */
node * words15 = NULL;
/* Arvore primaria que contem de 15 a 20 letras */
node * words20 = NULL;
/* Arvore primaria que contem de 20 e mais letras */
node * words25 = NULL;

/* Arvores secundarias */
/* Arvore secundaria que contem as palavras encontradas completas */
node * words2 = NULL;
/* Arvore secundaria que contem de 0 a 5 letras encontradas */
node * words2_5 = NULL;
/* Arvore secundaria que contem de 5 a 10 letras encontradas */
node * words2_10 = NULL;
/* Arvore secundaria que contem de 10 a 15 letras encontradas */
node * words2_15 = NULL;
/* Arvore secundaria que contem de 15 a 20 letras encontradas */
node * words2_20 = NULL;
/* Arvore secundaria que contem de 20 e mais letras encontradas */
node * words2_25 = NULL;

/* Variavel para calcular o tempo de execucao do programa */
double seconds;

/* Variavel necessaria para as funcoes de troca de mensagem da MPI */
int tag = 0;
/* Variavel que contem o valor do noh que enviou a mensagem */
int source;

/**/
int i;
/**/
int j;
/* Contador das palavras do arquivo */
int counter_word_total = 0;
/* Contador das palavras encontradas */
int counter_word_parcial = 0;
/**/
int words_size[46] = {0};
/* Variavel que contem a quantidade de nos */
int size;
/* Variavel que contem o id do noh */
int id;
/**/
int n;
/**/
int w;
/* Variavel que contem o id da thread dentro do noh */
int th_id;
/**/
int flag = 0;
/* Variavel que representa a porcentagem atingida */
int percent = 0;
/* Variavel que representa se ocorreu um erro durante operacoes */
int error;
/* Variavel que avisa se aos nos para terminarem */
int finish;
/* Variavel que indica o tamanho da palavra */
int len;
/**/
int count_total_words;
/**/
int count_parcial_words;

/**/
char c;

/* Struct que representa o tempo antes de comecar */
struct timeb time_bef;
/* Struct que representa o tempo apos uma operacao */
struct timeb time_aft;

/* Funcao que sera executado no noh raiz */
void root();
/* Funcao que trata as arvores de 0 a 5 letras */
void tree5();
/* Funcao que trata as arvores de 5 a 10 letras */
void tree10();
/* Funcao que trata as arvores de 10 a 15 letras */
void tree15();
/* Funcao que trata as arvores de 15 a 20 letras */
void tree20();
/* Funcao que trata as arvores de 20 letras para mais */
void tree25();

int main(int argc, char * argv[])
{
    /* Inicializacao da MPI */
    error = MPI_Init(&argc, &argv);
    if(error != MPI_SUCCESS)
    {
        printf("Error starting MPI program. Terminating.\n");
        MPI_Finalize();
        exit(1);
    }

    /* Coletando informacoes das threads */
    MPI_Comm_size(comm, &size);
    MPI_Comm_rank(comm, &id);

    /* Devido as funcoes utilizadas, necessitamos de pelo menos 6 nos */
    if(size < 6)
    {
        printf("Para execução deve-se ter pelo menos 6 nós.\n");
        MPI_Finalize();
        exit(1);
    }

    /* Executa o preprocessamento do arquivo, criando as arvores primarias */
    words = preProcessor(&counter_word_total, words_size,
                         &words5, &words10, &words15, &words20, &words25);

    count_parcial_words = 0;

    /* Dependendo do id do noh, executa a respectiva funcao */
    if(id == ROOT)
        root();
    else if(id % 5 == 1)
    {
        count_total_words = getSizeTree(words5);
        tree5();
    }
    else if(id % 5 == 2)
    {
        count_total_words = getSizeTree(words10);
        tree10();
    }
    else if(id % 5 == 3)
    {
        count_total_words = getSizeTree(words15);
        tree15();
    }
    else if(id % 5 == 4)
    {
        count_total_words = getSizeTree(words20);
        tree20();
    }
    else if(id % 5 == 0 && id != ROOT)
    {
        count_total_words = getSizeTree(words25);
        tree25();
    }

    /* Se for o noh raiz fecha o arquivo */
    if(id == ROOT)
    {
        fclose(pFile);
        ftime(&time_aft);
        seconds = (time_aft.time - time_bef.time) +
            (time_aft.millitm - time_bef.millitm) / 1000.0;
        printf("Time spent: %.3f sec\n", seconds);
    }

    MPI_Finalize();
    return 0;
}

/* Funcao que sera executado no noh raiz */
void root()
{
    pFile = fopen("res_word_3.txt", "w");

    char s[50] = {0};
    ftime(&time_bef);
    while(counter_word_parcial < counter_word_total)
    {
        MPI_Recv(&len, 1, MPI_INT, MPI_ANY_SOURCE, tag, comm, &status);
        source = status.MPI_SOURCE;
        MPI_Recv(&s, len, MPI_CHAR, source, tag, comm, &status);
        s[len] = '\0';

        node * aux = NULL;
        node * aux_tree = NULL;

        aux = find(s, words5);
        if(aux != NULL)
        {
            if(find(s, words2_5) == NULL)
            {
                words2_5 = insert(s, words2_5);
                while(aux->rowSub)
                {
                    aux_tree = findSub(getRowNode(aux), words, 0);
                    if(aux_tree->complete)
                    {
                        fprintf(pFile, "%s", aux_tree->word);
                        counter_word_parcial++;
                    }
                }
            }
        }

        aux = find(s, words10);
        if(aux != NULL)
        {
            if(find(s, words2_10) == NULL)
            {
                words2_10 = insert(s, words2_10);
                while(aux->rowSub)
                {
                    aux_tree = findSub(getRowNode(aux), words, 1);
                    if(aux_tree->complete)
                    {
                        fprintf(pFile, "%s", aux_tree->word);
                        counter_word_parcial++;
                    }
                }
            }
        }

        aux = find(s, words15);
        if(aux != NULL)
        {
            if(find(s, words2_15) == NULL)
            {
                words2_15 = insert(s, words2_15);
                while(aux->rowSub)
                {
                    aux_tree = findSub(getRowNode(aux), words, 2);
                    if(aux_tree == NULL)
                        /******* ERRO ACONTECE AQUI *********************************/
                        printf("aquiaquimesmo %s %d %s\n", s, source, aux->word);
                    if(aux_tree->complete)
                    {
                        fprintf(pFile, "%s", aux_tree->word);
                        counter_word_parcial++;
                    }
                }
            }
        }

        aux = find(s, words20);
        if(aux != NULL)
        {
            if(find(s, words2_20) == NULL)
            {
                words2_20 = insert(s, words2_20);
                while(aux->rowSub)
                {
                    aux_tree = findSub(getRowNode(aux), words, 3);
                    if(aux_tree->complete)
                    {
                        fprintf(pFile, "%s", aux_tree->word);
                        counter_word_parcial++;
                    }
                }
            }
        }

        aux = find(s, words25);
        if(aux != NULL)
        {
            if(find(s, words2_25) == NULL)
            {
                words2_25 = insert(s, words2_25);
                while(aux->rowSub)
                {
                    aux_tree = findSub(getRowNode(aux), words, aux->blocks);
                    if(aux_tree->complete)
                    {
                        fprintf(pFile, "%s", aux_tree->word);
                        counter_word_parcial++;
                    }
                }
            }
        }

        if (percent != (counter_word_parcial * 100) / counter_word_total)
        {
            percent = (counter_word_parcial * 100) / counter_word_total;
            ftime(&time_aft);
            seconds = (time_aft.time - time_bef.time) +
                (time_aft.millitm - time_bef.millitm) / 1000.0;
            printf("%d %d %d %.3f\n", counter_word_total,
                    counter_word_parcial, percent, seconds);
        }
    }
}

/* Funcao que trata as arvores de 0 a 5 letras */
void tree5()
{
    #pragma omp parallel private(th_id,i,j)
    {
        th_id = omp_get_thread_num();
        srand(id);
        n = rand();

        node * aux_sub = NULL;

        char chars[27] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                          'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                          'u', 'v', 'w', 'x', 'y', 'z', '{'};
        int r;
        th_id = omp_get_thread_num();

        srand(id);
        ftime(&time_bef);

        while (finish == 0)
        {
            char s[50] = {0};

            if (flag > 100000)
            {
                srand(id);
                flag = 0;
                r = rand() % 27;

                for (w = 0; w < 27; w++)
                    chars[w] = ((chars[w] - 'a') + r) % 27 + 'a';

            }

            for (i = 1; i <= 100; i++)
            {
                flag++;
                for (j = 1; j <= i; j++)
                {
                    n = rand() % 27;
                    c = chars[n];
                    s[j - 1] = c;

                    if (findN(s, words5, j) == NULL)
                        j = i;

                    #pragma omp critical
                    if ((aux_sub = find(s, words5)) != NULL)
                    {
                        if (find(s, words2_5) == NULL)
                        {
                            flag = 0;
                            words2_5 = insert(s, words2_5);

                            count_parcial_words++;
                            if(count_parcial_words == count_total_words)
                                finish = 1;

                            int l = strlen(s);
                            ftime(&time_aft);
                            seconds = (time_aft.time - time_bef.time) +
                                (time_aft.millitm - time_bef.millitm) / 1000.0;
                            if(l > 0 && seconds < TIMEOUT)
                            {
                                ftime(&time_bef);
                                MPI_Send(&l, 1, MPI_INT, ROOT, tag, comm);
                                MPI_Send(&s, l, MPI_CHAR, ROOT, tag, comm);
                            }
                        }
                    }
                }
            }
        }
    }
}

/* Funcao que trata as arvores de 5 a 10 letras */
void tree10()
{
    #pragma omp parallel private(th_id,i,j)
    {
        th_id = omp_get_thread_num();
        srand(id);
        n = rand();

        node * aux_sub = NULL;

        char chars[27] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                          'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                          'u', 'v', 'w', 'x', 'y', 'z', '{'};
        int r;
        th_id = omp_get_thread_num();

        srand(id);

        while (finish == 0)
        {
            char s[50] = {0};

            if (flag > 100000)
            {
                srand(id);
                flag = 0;
                r = rand() % 27;

                for (w = 0; w < 27; w++)
                    chars[w] = ((chars[w] - 'a') + r) % 27 + 'a';

            }

            for (i = 1; i <= 100; i++)
            {
                flag++;
                for (j = 1; j <= i; j++)
                {
                    n = rand() % 27;
                    c = chars[n];
                    s[j - 1] = c;

                    if (findN(s, words10, j) == NULL)
                        j = i;

                    #pragma omp critical
                    if ((aux_sub = find(s, words10)) != NULL)
                    {
                        if (find(s, words2_10) == NULL)
                        {
                            flag = 0;
                            words2_10 = insert(s, words2_10);

                            count_parcial_words++;
                            if(count_parcial_words == count_total_words)
                                finish = 1;

                            int l = strlen(s);
                            ftime(&time_aft);
                            seconds = (time_aft.time - time_bef.time) +
                                (time_aft.millitm - time_bef.millitm) / 1000.0;
                            if(l > 0 && seconds < TIMEOUT)
                            {
                                ftime(&time_bef);
                                MPI_Send(&l, 1, MPI_INT, ROOT, tag, comm);
                                MPI_Send(&s, l, MPI_CHAR, ROOT, tag, comm);
                            }
                        }
                    }
                }
            }
        }
    }
}

/* Funcao que trata as arvores de 10 a 15 letras */
void tree15()
{
    #pragma omp parallel private(th_id,i,j)
    {
        th_id = omp_get_thread_num();
        srand(id);
        n = rand();

        node * aux_sub = NULL;

        char chars[27] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                          'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                          'u', 'v', 'w', 'x', 'y', 'z', '{'};
        int r;
        th_id = omp_get_thread_num();

        srand(id);

        while (finish == 0)
        {
            char s[50] = {0};

            if (flag > 100000)
            {
                srand(id);
                flag = 0;
                r = rand() % 27;

                for (w = 0; w < 27; w++)
                    chars[w] = ((chars[w] - 'a') + r) % 27 + 'a';

            }

            for (i = 1; i <= 100; i++)
            {
                flag++;
                for (j = 1; j <= i; j++)
                {
                    n = rand() % 27;
                    c = chars[n];
                    s[j - 1] = c;

                    if (findN(s, words15, j) == NULL)
                        j = i;

                    #pragma omp critical
                    if ((aux_sub = find(s, words15)) != NULL)
                    {
                        if (find(s, words2_15) == NULL)
                        {
                            flag = 0;
                            words2_15 = insert(s, words2_15);

                            count_parcial_words++;
                            if(count_parcial_words == count_total_words)
                                finish = 1;

                            int l = strlen(s);
                            ftime(&time_aft);
                            seconds = (time_aft.time - time_bef.time) +
                                (time_aft.millitm - time_bef.millitm) / 1000.0;
                            if(l > 0 && seconds < TIMEOUT)
                            {
                                ftime(&time_bef);
                                MPI_Send(&l, 1, MPI_INT, ROOT, tag, comm);
                                MPI_Send(&s, l, MPI_CHAR, ROOT, tag, comm);
                            }
                        }
                    }
                }
            }
        }
    }
}

/* Funcao que trata as arvores de 15 a 20 letras */
void tree20()
{
    #pragma omp parallel private(th_id,i,j)
    {
        th_id = omp_get_thread_num();
        srand(id);
        n = rand();

        node * aux_sub = NULL;

        char chars[27] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                          'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                          'u', 'v', 'w', 'x', 'y', 'z', '{'};
        int r;
        th_id = omp_get_thread_num();

        srand(id);

        while (finish == 0)
        {
            char s[50] = {0};

            if (flag > 100000)
            {
                srand(id);
                flag = 0;
                r = rand() % 27;

                for (w = 0; w < 27; w++)
                    chars[w] = ((chars[w] - 'a') + r) % 27 + 'a';

            }

            for (i = 1; i <= 100; i++)
            {
                flag++;
                for (j = 1; j <= i; j++)
                {
                    n = rand() % 27;
                    c = chars[n];
                    s[j - 1] = c;

                    if (findN(s, words20, j) == NULL)
                        j = i;

                    #pragma omp critical
                    if ((aux_sub = find(s, words20)) != NULL)
                    {
                        if (find(s, words2_20) == NULL)
                        {
                            flag = 0;
                            words2_20 = insert(s, words2_20);

                            count_parcial_words++;
                            if(count_parcial_words == count_total_words)
                                finish = 1;

                            int l = strlen(s);
                            ftime(&time_aft);
                            seconds = (time_aft.time - time_bef.time) +
                                (time_aft.millitm - time_bef.millitm) / 1000.0;
                            if(l > 0 && seconds < TIMEOUT)
                            {
                                ftime(&time_bef);
                                MPI_Send(&l, 1, MPI_INT, ROOT, tag, comm);
                                MPI_Send(&s, l, MPI_CHAR, ROOT, tag, comm);
                            }
                        }
                    }
                }
            }
        }
    }
}

/* Funcao que trata as arvores de 20 letras para mais */
void tree25()
{
    #pragma omp parallel private(th_id,i,j)
    {
        th_id = omp_get_thread_num();
        srand(id);
        n = rand();

        node * aux_sub = NULL;

        char chars[27] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
                          'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                          'u', 'v', 'w', 'x', 'y', 'z', '{'};
        int r;
        th_id = omp_get_thread_num();

        srand(id);

        while (finish == 0)
        {
            char s[50] = {0};

            if (flag > 100000)
            {
                srand(id);
                flag = 0;
                r = rand() % 27;

                for (w = 0; w < 27; w++)
                    chars[w] = ((chars[w] - 'a') + r) % 27 + 'a';

            }

            for (i = 1; i <= 100; i++)
            {
                flag++;
                for (j = 1; j <= i; j++)
                {
                    n = rand() % 27;
                    c = chars[n];
                    s[j - 1] = c;

                    if (findN(s, words25, j) == NULL)
                        j = i;

                    #pragma omp critical
                    if ((aux_sub = find(s, words25)) != NULL)
                    {
                        if (find(s, words2_25) == NULL)
                        {
                            flag = 0;
                            words2_25 = insert(s, words2_25);

                            count_parcial_words++;
                            if(count_parcial_words == count_total_words)
                                finish = 1;

                            int l = strlen(s);
                            ftime(&time_aft);
                            seconds = (time_aft.time - time_bef.time) +
                                (time_aft.millitm - time_bef.millitm) / 1000.0;
                            if(l > 0 && seconds < TIMEOUT)
                            {
                                ftime(&time_bef);
                                MPI_Send(&l, 1, MPI_INT, ROOT, tag, comm);
                                MPI_Send(&s, l, MPI_CHAR, ROOT, tag, comm);
                            }
                        }
                    }
                }
            }
        }
    }
}
